/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.beans.bind;

import java.beans.PropertyDescriptor;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

/**
 * Provides a simplified means to support field property mappings requiring subclasses to
 * only implement the {@link #findPropertyName(Class, String)} method.  This class takes care
 * of providing the full field to property mappings and vice versa.
 * 
 * @author Scott Rossillo
 *
 */
public abstract class FieldPropertyMappingHandlerSupport implements FieldPropertyMappingHandler {

	/**
	 * Creates a new abstract field property mapper. 
	 */
	protected FieldPropertyMappingHandlerSupport() { }

	
	/* (non-Javadoc)
	 * @see net.martinimix.beans.bind.FieldPropertyMappingHandler#mapFieldsToProperties(java.lang.Object)
	 */
	public final Map mapFieldsToProperties(Object bean) {
		
		return createCacheableFieldToPropertyMap(bean);
	}
	
	/* (non-Javadoc)
	 * @see net.martinimix.beans.bind.FieldPropertyMappingHandler#mapPropertiesToFields(java.lang.Object)
	 */
	public final Map mapPropertiesToFields(Object bean) {
		
		return this.createCacheablePropertyToFieldMap(bean);
	}

	/**
	 * Returns a cacheable field to property map for the given bean.
	 */
	private final Map createCacheableFieldToPropertyMap(Object bean) {
		
		final Map fieldToPropertyMap = new HashMap(); 
		final BeanWrapper wrapper = new BeanWrapperImpl(bean);
		final PropertyDescriptor fields[] = wrapper.getPropertyDescriptors();
		
		String fieldName;
		String propertyName;
	
		for(int i = 0; i < fields.length; i++) {
	
			fieldName = fields[i].getName();
			propertyName = findPropertyName(bean.getClass(), fieldName);
			
			if(propertyName != null) {
				fieldToPropertyMap.put(fieldName, propertyName);
			}
		}
		
		return Collections.unmodifiableMap(fieldToPropertyMap);
	}
	
	/**
	 * Returns a property to field map for the given bean.
	 * 
	 * @param bean the bean for which to provide a field to property map
	 * 
	 * @return a <code>Map</code> containing property to field mappings
	 * for the given <code>bean</code>
	 */
	private final Map createCacheablePropertyToFieldMap(Object bean) {
		
		final Map fieldToPropertyMap = this.mapFieldsToProperties(bean);
		final Map propertyToFieldMap = new HashMap();
		
		String field;
		String property;
		
		for(Iterator it = fieldToPropertyMap.keySet().iterator(); it.hasNext(); ) {
			field = (String) it.next();
			property = (String) fieldToPropertyMap.get(field);
			
			propertyToFieldMap.put(property, field);
		}
		
		return Collections.unmodifiableMap(propertyToFieldMap);
	}

	/**
	 * Returns the name of the property for the given class and field name.
	 * 
	 * @param clazz the <code>Class</code> whose <code>field</code> property
	 * name should be returned
	 *  
	 * @param field the name of the field whose property name should be returned 
	 * 
	 * @return the property name for the given <code>clazz</code> and <code>field</code>
	 */
	protected abstract String findPropertyName(Class clazz, String field);

}
